//=============================================================================
// ULDS.js
//=============================================================================

/*:
* @target MZ
* @plugindesc Unlimited Layer Display System.
* @author taroxd
*
* @param Default Path
* @desc The default path where pictures are stored.
* @default parallaxes
*
* @param Default Z
* @desc The default Z coordinate for sprites.
* @type number
* @decimals 2
* @min -15
* @max 15
* @default 0.5
*
* @help This plugin does not provide plugin commands.
*
* Map Note: <ulds> JSON </ulds>
* "name": picture filename
* "path": picture path (default to Default Path)
* "loop": true/false
*    Whether the picture should loop.
* "hue"/"smooth": the attribute of a Bitmap.
* <attribute>: the attribute of a Sprite.
*
* A string can be used as a value to be interpreted as a formula.
* In the formula, 't' refers to frame count.
*                 's' refers to $gameSwitches.
*                 'v' refers to $gameVaribles.
*
* Also, various helpers are available, defined in Helper.
*
* If a string is used as a formula of the attribute,
* the attribute will be updated every frame.
*
* By the way, attributes such as "anchor.x" is also available.
*
*
* Here is an example:
*
  <ulds> {
    "name": "BlueSky",
    "x": "this.rx(t)",
    "y": 50,
    "rotation": "this.rr(0.01)",
    "anchor.x": 0.5,
    "anchor.y": 0.5,
    "loop": true
  } </ulds>
*
*/


void function() {

   var assign = Object.assign || function(target) {
       for (var i = 1; i < arguments.length; i++) {
           var source = arguments[i];
           for (var key in source) {
               target[key] = source[key];
           }
       }
       return target;
   };

   var RE = /<ulds>([^]*?)<\/ulds>/ig;
   var parameters = PluginManager.parameters('ULDS');
   var DEFAULT_SETTINGS = {
       z: parseFloat(parameters['Default Z']),
       path: parameters['Default Path'],
       smooth: true
   };

   // Feel free to add your own helper.
   var Helper = {
       t: 0,

       // Converts a coordinate on the map to the corresponding coordinate on the screen.
       rx: function(x, scrollRate) {
           if (scrollRate == null) {
               scrollRate = $gameMap.tileWidth();
           }

           if (scrollRate === 0) {
               return x;
           } else {
               return $gameMap.adjustX(x / scrollRate) * scrollRate;
           }
       },

       ry: function(y, scrollRate) {
           if (scrollRate == null) {
               scrollRate = $gameMap.tileHeight();
           }

           if (scrollRate === 0) {
               return y;
           } else {
               return $gameMap.adjustY(y / scrollRate) * scrollRate;
           }
       },

       rotate: 0,

       rr: function(degree) {
          this.rotate = this.rotate + degree;
          return this.rotate;
       },

       update: function() {
           ++this.t;
           this._updater(this.t, $gameSwitches, $gameVariables);
       },

       assignSettings: function(settings) {
           var code = '';
           for (var key in settings) {
               var value = settings[key];
               if (typeof(value) === 'string') {
                   // this.x = (formula);
                   // this.scale.x = (formula); // key is "scale.x"
                   code += 'this.' + key + ' = (' + value + ');\n';
               } else {
                   // if key is "scale.x"
                   // keys is ["scale", "x"]
                   var keys = key.split('.');
                   // set key to "x"
                   key = keys.pop();

                   var target = this;
                   keys.forEach(function(k) {
                       if (typeof(target) !== 'object') {
                           target[k] = {};
                       }
                       target = target[k];
                   });

                   target[key] = value;
               }
           }
           // You may log the code for debugging purpose.
           // console.log(code);
           this._updater = new Function('t', 's', 'v', code);
       }
   };

   // NOT a class constructor
   function ULDS(settings) {
       settings = assign({}, DEFAULT_SETTINGS, settings);
       var spriteClass = settings.loop ? ULDS.TilingSprite : ULDS.Sprite;
       var bitmap = ImageManager.loadBitmap('img/' + settings.path + '/',
           settings.name, settings.hue, settings.smooth);
       var sprite = new spriteClass(bitmap);

       delete settings.path;
       delete settings.name;
       delete settings.loop;
       delete settings.hue;
       delete settings.smooth;

       sprite.assignSettings(settings);

       return sprite;
   }

   ULDS.Sprite = function(bitmap) {
       Sprite.call(this, bitmap);
   };

   ULDS.Sprite.prototype = Object.create(Sprite.prototype);
   ULDS.Sprite.prototype.constructor = ULDS.Sprite;
   assign(ULDS.Sprite.prototype, Helper);

   ULDS.TilingSprite = function(bitmap) {
       TilingSprite.call(this, bitmap);
       bitmap.addLoadListener(function() {
           this.move(0, 0, bitmap.width, bitmap.height);
       }.bind(this));
   };

   ULDS.TilingSprite.prototype = Object.create(TilingSprite.prototype);
   ULDS.TilingSprite.prototype.constructor = ULDS.TilingSprite;
   assign(ULDS.TilingSprite.prototype, Helper);

   Object.defineProperties(ULDS.TilingSprite.prototype, {
       x: {
           get: function() { return -this.origin.x; },
           set: function(x) { this.origin.x = -x; }
       },
       y: {
           get: function() { return -this.origin.y; },
           set: function(y) { this.origin.y = -y; }
       }
   });

   var ct = Spriteset_Map.prototype.createTilemap;
   Spriteset_Map.prototype.createTilemap = function() {
       ct.call(this);
       $dataMap.note.replace(RE, function(_match, settings) {
           var isValid = false;
           try {
               settings = JSON.parse(settings);
               isValid = typeof(settings) === 'object';
               if (!isValid) {
                   throw 'ULDS settings should be an object';
               }
           } catch (e) {
               console.error(e);
               console.log(settings);
           }
           if (isValid) {
               this._tilemap.addChild(ULDS(settings));
           }
       }.bind(this));
   };
}();
